package com.pic.mgt.haishin.manager.terminal;

import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;

import com.pic.mgt.dao.ITerminalMgtDao;
import com.pic.mgt.dao.TerminalMgtDao;
import com.pic.mgt.log.IgxLog;
import com.pic.mgt.manager.terminal.BasicTerminalInfoBean;
import com.pic.mgt.manager.terminal.SimpleTerminalManager;
import com.pic.mgt.manager.terminal.SynchronizeTerminalsException;
import com.pic.mgt.manager.terminal.TerminalManagementOperationException;
import com.pic.mgt.terminal.operation.TerminalOperation;
import com.pic.mgt.terminal.operation.TerminalOperationException;
import com.pic.mgt.utils.DateConvertUtils;

public final class SimpleHaishinTerminalManager extends
		SimpleTerminalManager implements HaishinTerminalManager {
	
	private TerminalOperation terminalOperation;
	
	private final String effectiveTimeStringFormat = "yyyyMMddHHmmss";
	
	public SimpleHaishinTerminalManager(ITerminalMgtDao terminalMgtDao,
		    					        String haishinOutboxPathStr,
										int terminalIdLen)
										throws TerminalManagementOperationException {
		super(terminalMgtDao, haishinOutboxPathStr, terminalIdLen);
	}
	
	@Override
	protected void synchronizeAddTerminals(Map<String, Object> terminalFolderIdMap,
										   Map<String, BasicTerminalInfoBean> basicTerminalInfoBeanInDbMap)
										 throws TerminalManagementOperationException {
		// 如果DB的端末ID不存在於目錄中， 則必須新增。

		Set<String> terminalIdInDbSet = basicTerminalInfoBeanInDbMap.keySet();
		for (String terminalIdInDb : terminalIdInDbSet) {
			BasicTerminalInfoBean basicTerminalInfoBeanInDb =
								  basicTerminalInfoBeanInDbMap.get(terminalIdInDb);
			if (!terminalFolderIdMap.containsKey(terminalIdInDb)) {
				// DB的端末ID不存在於目錄中，必須進行新增
				try {
					// throws DataAccessException, TerminalOperationException
					doSynchronizeAddTerminalAction(basicTerminalInfoBeanInDb);
					
					// 正確完成新增"後",通知 TerminalListener
					this.terminalManagementListener.
							afterSynchronizeAddedTerminalNotify(basicTerminalInfoBeanInDb);
				} catch (SynchronizeTerminalsException e2) {
					try {
						throw new TerminalManagementOperationException(
								"SUDA-239 Adding terminal '" + terminalIdInDb
								+ "' folder failed.", e2);
					} catch (TerminalManagementOperationException e) {
						// ********************** Log **********************
						IgxLog.messageLog.error(this, e.getMessage());
						// ********************** Log **********************

						throw e;
					}
				}
			}
			else {
				// 有該DB的端末ID的目錄存在，不需做新增動作。但需判斷清單中是否有此端末在內,做對應的處理.
				if (super.basicTerminalInfoBeanMap.containsKey(terminalIdInDb)) {
					// 更新端末清單內的端未資訊
					super.updateBasicTerminalInfoBean(
									super.basicTerminalInfoBeanMap.get(terminalIdInDb),
									basicTerminalInfoBeanInDb);
				}
				else {
					// (此種情況應不會發生)
					super.basicTerminalInfoBeanMap.put(terminalIdInDb,
													   basicTerminalInfoBeanInDb);
				}
			}
		}
	}
	
	@Override
	protected void synchronizeRemoveTerminals(Map<String, Object> terminalFolderIdMap,
											Map<String, BasicTerminalInfoBean> basicTerminalInfoBeanIdDbMap)
											throws TerminalManagementOperationException {
		// 如果端末目錄不存在於DB記錄中， 則必須刪除。

		Set<String> terminalFolderIdSet = terminalFolderIdMap.keySet();
		for (String terminalFolderId : terminalFolderIdSet) {
			if (!basicTerminalInfoBeanIdDbMap.containsKey(terminalFolderId)) {
				try {
					// 進行刪除
					
					// 要刪除"前"先通知 TerminalListener
					this.terminalManagementListener.
							beforeSynchronizeRemovedTerminalNotify(terminalFolderId);
					
					// throws DataAccessException, TerminalOperationException
					doSynchronizeRemoveTerminalAction(terminalFolderId);
				} catch (DataAccessException e) {
					// 不會發生此種 Exception
				} catch (SynchronizeTerminalsException e2) {
					try {
						throw new TerminalManagementOperationException(
								"SUDA-240 Removing terminal '" + terminalFolderId +
								"' folder failed.", e2);
					} catch (TerminalManagementOperationException e) {
						// ********************** Log **********************
						IgxLog.messageLog.error(this, e.getMessage());
						// ********************** Log **********************

						throw e;
					}
				}
			}
		}
	}
	
	private void doSynchronizeAddTerminalAction(
							BasicTerminalInfoBean toBeAddedBasicTerminalInfoBean)
							throws SynchronizeTerminalsException {
		
		try {
			// throws DataAccessException, TerminalOperationException
			doAddTerminal(toBeAddedBasicTerminalInfoBean, false);
		} catch (DataAccessException e1) {
			throw new SynchronizeTerminalsException("", e1);
		} catch (TerminalOperationException e2) {
			throw new SynchronizeTerminalsException("", e2); 
		}
	}
	
	@Override
	public synchronized void addTerminal(String newTerminalId)
				throws TerminalManagementOperationException {
		// 未給定生效開始時間，所以以現在時間為生效開始時間
		Date effectiveStartTime = new Date();
		
		// throws TerminalManagementOperationException
		addTerminal(newTerminalId, effectiveStartTime);
	}
	
	@Override
	public synchronized void addTerminal(String newTerminalId,
							String effectiveStartTimeString)
							throws TerminalManagementOperationException {
		// 將日期字串轉換為 Date
		Date effectiveStartTime;
		try {
			// throws StringIndexOutOfBoundsException, NumberFormatException
			
			effectiveStartTime = DateConvertUtils.convertToDate(effectiveStartTimeString,
															    this.effectiveTimeStringFormat);
		} catch (Exception e) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-241 Creating terminal '" + newTerminalId +
						"' operation failed in coverting date string operation.",
						e);
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************

				throw e1;
			}
		} 
		
		// throws TerminalManagementOperationException
		addTerminal(newTerminalId, effectiveStartTime);
	}
	
	@Override
	public synchronized void addTerminal(String newTerminalId, Date effectiveStartTime)
								throws TerminalManagementOperationException {
		// throws TerminalManagementOperationException
		addTerminal(newTerminalId, effectiveStartTime, null);
	}
	
	@Override
	public synchronized void addTerminal(String newTerminalId,
							String effectiveStartTimeString, String effectiveEndTimeString)
							throws TerminalManagementOperationException {
		// 將日期字串轉換為 Date
		Date effectiveStartTime;
		Date effectiveEndTime;
		try {
			// throws StringIndexOutOfBoundsException, NumberFormatException
			
			effectiveStartTime = DateConvertUtils.convertToDate(effectiveStartTimeString,
															    this.effectiveTimeStringFormat);
			effectiveEndTime = DateConvertUtils.convertToDate(effectiveEndTimeString,
					 										  this.effectiveTimeStringFormat);
			
		} catch (Exception e) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-241 Creating terminal '" + newTerminalId +
						"' operation failed in coverting date string operation.",
						e);
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************

				throw e1;
			}
		} 
		
		// throws TerminalManagementOperationException
		addTerminal(newTerminalId, effectiveStartTime, effectiveEndTime);
	}
	
	@Override
	public synchronized void addTerminal(String newTerminalId, Date effectiveStartTime, Date effectiveEndTime)
		
	throws TerminalManagementOperationException {
		// 檢查端末ID和有效開始時間是否為null
		if (null == newTerminalId || null == effectiveStartTime) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-242 Adding terminal failed: didn't provide new terminal id or effective starting time.");
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************

				throw e1;
			}
		}
		
		// 檢查端末ID長度是否正確
		if (newTerminalId.length() != super.terminalIdLen) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-243 Adding terminal failed: the length of new terminal id '" +
						newTerminalId + "' is incorrect.");
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************

				throw e1;
			}
		}
		
		BasicTerminalInfoBean newBasicTerminalInfoBean =
									createBasicTerminalInfoBean(newTerminalId,
																effectiveStartTime,
																effectiveEndTime);
		
		try {
			doAddTerminal(newBasicTerminalInfoBean, true);
			
			// 正確完成新增"後",通知 TerminalListener
			this.terminalManagementListener.
					afterDynamicAddedTerminalNotify(newBasicTerminalInfoBean);

		} catch (DataAccessException e2) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-244 Creating terminal '" + newTerminalId +
						"' operation failed in database operation.",
						e2);
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************

				throw e1;
			}
		} catch (TerminalOperationException e) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-245 Creating terminal '" + newTerminalId +
						"' operation failed in folder operation.",
						e);
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************
				
				throw e1;
			}
		}
	}
	
	private BasicTerminalInfoBean createBasicTerminalInfoBean (String terminalId,
															   Date effectiveStartTime,
															   Date effectiveEndTime) {
		BasicTerminalInfoBean basicTerminalInfoBean = new BasicTerminalInfoBean();

		basicTerminalInfoBean.setTerminalId(terminalId);
		basicTerminalInfoBean.setEffectiveStartTime(effectiveStartTime);
		basicTerminalInfoBean.setEffectiveEndTime(effectiveEndTime);

		return basicTerminalInfoBean;
	}
	
	/**
	 * 進行新增端末的動作（包含：新增端末至資料庫,新增目錄,更新端末清單）
	 * @param toBeAddedBasicTerminalInfoBean
	 * @param isIncludeDbInsert true:要新增至資料庫
	 * @throws DataAccessException
	 * @throws TerminalOperationException
	 */
	private void doAddTerminal(BasicTerminalInfoBean toBeAddedBasicTerminalInfoBean,
											  boolean isIncludeDbInsert)
						throws DataAccessException, TerminalOperationException {
		
		/*
		 * (1) 新增端末至 DB (如果 isIncludeDbDelete 為 true)
		 * (2) 新增端末目錄
		 * (3) 從端末清單中新增端末
		 */
		
		String newTerminalId = toBeAddedBasicTerminalInfoBean.getTerminalId();
		
		synchronized (super.basicTerminalInfoBeanMap) {
			// 新增端末至 DB
			if (isIncludeDbInsert) {
				try {
					/* throws DataAccessException,DuplicateKeyException
					 * 若是 DuplicateKeyException 則忽略。
					 */
					insertTerminalToDB(toBeAddedBasicTerminalInfoBean);
					
					// ********************** Log **********************
					if (IgxLog.messageLog.isDebugMode()) {
						IgxLog.messageLog.debug(this,
								"Inserted terminal record '{}' to database.",
								toBeAddedBasicTerminalInfoBean.getTerminalId());
					}
					// ********************** Log **********************
				} catch (DuplicateKeyException e1) {
					// 代表資料庫內已經存在該端末的資料(寫Log後，忽略之，繼續往下做。)
					
					// ********************** Log **********************
					if (IgxLog.messageDetailsLog.isWarnMode()) {
						IgxLog.messageDetailsLog.warn(this, e1.getMessage());
					}
					// ********************** Log **********************
					
					// ********************** Log **********************
					if (IgxLog.messageLog.isWarnMode()) {
						IgxLog.messageLog.warn(this,
								"Terminal record '{}' already existed in database.",
								newTerminalId);
					}
					// ********************** Log **********************
				} catch (DataAccessException e2) {
					// ********************** Log **********************
					IgxLog.messageDetailsLog.error(this, e2.getMessage());
					// ********************** Log **********************
					
					throw e2;
				}
			}
			
			
			// ------------------------ 以上為 資料庫 新增的動作 -------------------------
			
			
			// 新增端末目錄
			try {
				// throws TerminalOperationException
				File createdTerminalFolder =
					this.terminalOperation.createTerminalFolder(newTerminalId);
				
				// ********************** Log **********************
				if (IgxLog.messageLog.isDebugMode()) {
					IgxLog.messageLog.debug(this,
							"Created terminal folder '{}'.",
							createdTerminalFolder.getAbsolutePath());
				}
				// ********************** Log **********************
			} catch (TerminalOperationException e) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e.getMessage());
				// ********************** Log **********************
				
				throw e;
			} finally {
				/*
				 *  從端末清單中新增端末。因為已經從 DB 將端末的記錄新增，所以無論
				 *  如何，清單也要新增(即便在目錄建立失敗的情況下)。
				 */
				
				super.basicTerminalInfoBeanMap.put(
						newTerminalId, 
						toBeAddedBasicTerminalInfoBean);
			}
		}
		// ********************** Log **********************
		IgxLog.messageLog.info(this,
				"Creating terminal '{}' operation completed.", newTerminalId);
		// ********************** Log **********************
	}
	
	private void insertTerminalToDB(BasicTerminalInfoBean toBeAddedBasicTerminalInfoBean)
									throws DataAccessException {
		Map<String, Object> insertValuesMap = new HashMap<String, Object>();
		insertValuesMap.put(TerminalMgtDao._terminalId, toBeAddedBasicTerminalInfoBean.getTerminalId());
		insertValuesMap.put(TerminalMgtDao._effectiveStartTime, toBeAddedBasicTerminalInfoBean.getEffectiveStartTime());
		insertValuesMap.put(TerminalMgtDao._effectiveEndTime, toBeAddedBasicTerminalInfoBean.getEffectiveEndTime());

		super.terminalMgtDao.insertBasic(insertValuesMap);
	}
	
	private void doSynchronizeRemoveTerminalAction(String toBeRemovedTerminalId)
			throws SynchronizeTerminalsException {
		try {
			doRemoveTerminal(toBeRemovedTerminalId, false);
		} catch (DataAccessException e) {
			throw new SynchronizeTerminalsException("" ,e);
		} catch (TerminalOperationException e) {
			throw new SynchronizeTerminalsException("" ,e);
		}
	}

	@Override
	public synchronized void removeTerminal(String toBeRemovedTerminalId)
							   throws TerminalManagementOperationException {
		// 檢查端末ID是否為null
		if (null == toBeRemovedTerminalId) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-246 Removing terminal failed: didn't provide terminal id to be removed.");
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************

				throw e1;
			}
		}
		
		try {
			// 要刪除"前"先通知 TerminalListener
			this.terminalManagementListener.
					beforeDynamicRemovedTerminalNotify(toBeRemovedTerminalId);
			
			doRemoveTerminal(toBeRemovedTerminalId, true);
		} catch (DataAccessException e) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-247 Removing terminal '" + toBeRemovedTerminalId +
						"' operation failed in database operation.",
						e);
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************
				
				throw e1;
			}
		} catch (TerminalOperationException e) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-248 Removing terminal '" + toBeRemovedTerminalId +
						"' operation failed in folder operation.",
						e);
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************
				
				throw e1;
			}
		}
	}
	
	/**
	 * 移除端末
	 * @param toBeRemovedTerminalIdStr
	 * @param isIncludeDbDelete 是否包含 DB 中資料的移除。
	 * @throws DataAccessException
	 * @throws TerminalOperationException
	 */
	private void doRemoveTerminal (String toBeRemovedTerminalIdStr, boolean isIncludeDbDelete)
								   throws DataAccessException, TerminalOperationException {
		/*
		 * (1) 移除 DB (如果 isIncludeDbDelete 為 true)
		 * (2) 移除目錄
		 * (3) 從端末清單中移除端末
		 */
		
		synchronized (super.basicTerminalInfoBeanMap) {
			// 移除 DB 內的端末
			if (isIncludeDbDelete) {
				try {
					// throws DataAccessException
					int deletedCount =
						deleteTerminalFromDB(toBeRemovedTerminalIdStr);
					if (deletedCount <= 0) {
						// 代表資料庫裡原本就沒有該筆端末的存在
						
						// ********************** Log **********************
						if (IgxLog.messageLog.isWarnMode()) {
							IgxLog.messageLog.warn(this,
									"There is no terminal record '{}' in database that can be deleted.",
									toBeRemovedTerminalIdStr);
						}
						// ********************** Log **********************
					}
					else {
						// ********************** Log **********************
						if (IgxLog.messageLog.isDebugMode()) {
							IgxLog.messageLog.debug(this,
									"Deleted terminal record '{}' from database.", toBeRemovedTerminalIdStr);
						}
						// ********************** Log **********************
					}
				} catch (DataAccessException e) {
					// ********************** Log **********************
					IgxLog.messageDetailsLog.error(this, e.getMessage());
					// ********************** Log **********************
					
					throw e;
				}
			}
			
			// 移除目錄
			try {
				// throws TerminalOperationException
				File removedTerminalFolder =
					this.terminalOperation.removeTerminalFolder(toBeRemovedTerminalIdStr);
				
				// ********************** Log **********************
				if (IgxLog.messageLog.isDebugMode()) {
					IgxLog.messageLog.debug(this,
							"Removed terminal folder '{}'.",
							removedTerminalFolder.getAbsolutePath());
				}
				// ********************** Log **********************
			} catch (TerminalOperationException e) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e.getMessage());
				// ********************** Log **********************
				
				throw e;
			} finally {
				/*
				 *  從端末清單中移除端末。因為已經從 DB 將端末的記錄移除，所以無論
				 *  如何，都要從清單中移除(即便在目錄移除失敗的情況下)。
				 */
				super.basicTerminalInfoBeanMap.remove(toBeRemovedTerminalIdStr);
			}
		}
		
		// ********************** Log **********************
		IgxLog.messageLog.info(this,
				"Removing terminal '{}' operation completed.", toBeRemovedTerminalIdStr);
		// ********************** Log **********************
	}
	
	private int deleteTerminalFromDB(String terminalId) throws DataAccessException {
		return
			super.terminalMgtDao.deleteByTerminalId(terminalId);
	}
	
	// --- getter and setter ---
	
	public void setTerminalOperation(TerminalOperation terminalOperation) {
		this.terminalOperation = terminalOperation;
	}

}
